using System;
using System.IO;
using System.Runtime.InteropServices;
using UnityEngine;

public sealed class ByteStreamFormatter : IDisposable {
	MemoryStream _ms;
	BinaryReader _br;
	BinaryWriter _bw;

	/// <summary>
	/// Создаем форматтер для чтения, из уже имеющихся данных
	/// </summary>
	/// <param name="data"></param>
	public ByteStreamFormatter(byte[] data) {
		_ms = new MemoryStream(data);
		_br = new BinaryReader(_ms);
		_bw = new BinaryWriter(_ms);
	}

	public ByteStreamFormatter() {
		_ms = new MemoryStream(16);
		_br= new BinaryReader(_ms);
		_bw= new BinaryWriter(_ms);
	}

	public byte[] GetData() {
		return _ms.ToArray();
	}

	public void Dispose() {
		if ( _ms != null) {
			_ms.Dispose();
		}
		if ( _bw != null ) {
			_bw.Dispose();
		}
		if ( _br!= null ) {
			_br.Dispose();
		}
	}

	public bool Read_Bool() {
		return _br.ReadBoolean();
	}

	public byte Read_Byte() {
		return _br.ReadByte();
	}

	public ushort Read_Ushort() {
		return _br.ReadUInt16();
	}

	public uint Read_Uint() {
		return _br.ReadUInt32();
	}

	public ulong Read_Ulong() {
		return _br.ReadUInt64();
	}

	public long Read_Long() {
		return _br.ReadInt64();
	}

	public int Read_Int() {
		return _br.ReadInt32();
	}
	
	public short Read_Short() {
		return _br.ReadInt16();
	}

	public float Read_Float() {
		return _br.ReadSingle();
	}

	public double Read_Double() {
		return _br.ReadDouble();
	}

	public string Read_String() {
		return _br.ReadString();
	}

	public Vector2 Read_Vector2() {
		var x = _br.ReadSingle();
		var y = _br.ReadSingle();
		return new Vector2(x, y);
	}

	public Vector3 Read_Vector3() {
		var x = _br.ReadSingle();
		var y = _br.ReadSingle();
		var z = _br.ReadSingle();
		return new Vector3(x, y, z);
	}

	public Vector4 Read_Vector4() {
		var x = _br.ReadSingle();
		var y = _br.ReadSingle();
		var z = _br.ReadSingle();
		var w = _br.ReadSingle();
		return new Vector4(x, y, z, w);
	}

	public Vector2Int Read_Vector2Int() {
		var x = _br.ReadInt32();
		var y = _br.ReadInt32();
		return new Vector2Int(x, y);
	}

	public Vector3Int Read_Vector3Int() {
		var x = _br.ReadInt32();
		var y = _br.ReadInt32();
		var z = _br.ReadInt32();
		return new Vector3Int(x, y, z);
	}

	public Color32 Read_Color32() {
		var r = _br.ReadByte();
		var g = _br.ReadByte();
		var b = _br.ReadByte();
		var a = _br.ReadByte();
		return new Color32(r, g, b, a);
	}

	public Quaternion Read_Quaternion() {
		var x = _br.ReadSingle();
		var y = _br.ReadSingle();
		var z = _br.ReadSingle();
		var w = _br.ReadSingle();
		return new Quaternion(x, y, z, w);
	}

	public byte[] Read_Bytes() {
		var len = _br.ReadUInt16();
		if ( len == 0 ) {
			return Array.Empty<byte>();
		}
		return _br.ReadBytes(len);
	}

	public T[] Read_Array<T>() where T : unmanaged {
		var len = _br.ReadUInt16();
		var result = new T[len];

		for ( int i = 0; i < len; i++ ) {
			result[i] = Read_Unmanaged<T>();
		}
		return result;
	}


	public T Read_Unmanaged<T>() where T : unmanaged {
		int size = Marshal.SizeOf<T>();
		var bytes = _br.ReadBytes(size);
		var ptr = Marshal.AllocHGlobal(size);
		try {
			Marshal.Copy(bytes, 0, ptr, size);
			T result = Marshal.PtrToStructure<T>(ptr);
			return result;
		} finally {
			Marshal.FreeHGlobal(ptr);
		}
	}

	public void Write_Unmanaged<T>(T input) where T : unmanaged {
		int size = Marshal.SizeOf<T>();
		byte[] bytes = new byte[size];
		var ptr = Marshal.AllocHGlobal(size);
		try {
			Marshal.StructureToPtr(input, ptr, false);
			Marshal.Copy(ptr, bytes, 0, size);
		} finally {
			Marshal.FreeHGlobal(ptr);
		}
		_bw.Write(bytes);
	}

	public void Write_Array<T>(T[] array) where T : unmanaged {
		if ( array== null || array.Length == 0 ) {
			_bw.Write((ushort)0);
			return;
		}
		_bw.Write((ushort)array.Length);
		for ( int i = 0; i < array.Length; i++ ) {
			Write_Unmanaged<T>(array[i]);
		}
	}

	public void Write_Bool(bool value) {
		_bw.Write(value);
	}

	public void Write_Byte(byte value) {
		_bw.Write(value);
	}

	public void Write_Ushort(ushort value) {
		_bw.Write(value);
	}

	public void Write_Uint(uint value) {
		_bw.Write(value);
	}

	public void Write_Ulong(ulong value) {
		_bw.Write(value);
	}
	
	public void Write_Short(short value) {
		_bw.Write(value);
	}

	public void Write_Int(int value) {
		_bw.Write(value);
	}

	public void Write_Long(long value) {
		_bw.Write(value);
	}

	public void Write_Float(float value) {
		_bw.Write(value);
	}

	public void Write_Double(double value) {
		_bw.Write(value);
	}

	public void Write_String(string value) {
		_bw.Write(value);
	}

	public void Write_Bytes(byte[] bytes) {
		if ( bytes == null || bytes.Length == 0 ) {
			_bw.Write((ushort)0);
			return;
		}
		_bw.Write((ushort)bytes.Length);
		_bw.Write(bytes);
	}

	public void Write_Vector2Int(Vector2Int value) {
		_bw.Write(value.x);
		_bw.Write(value.y);
	}

	public void Write_Vector3Int(Vector3Int value) {
		_bw.Write(value.x);
		_bw.Write(value.y);
		_bw.Write(value.z);
	}

	public void Write_Vector2(Vector2 value) {
		_bw.Write(value.x);
		_bw.Write(value.y);
	}

	public void Write_Vector3(Vector3 value) {
		_bw.Write(value.x);
		_bw.Write(value.y);
		_bw.Write(value.z);
	}

	public void Write_Vector4(Vector4 value) {
		_bw.Write(value.x);
		_bw.Write(value.y);
		_bw.Write(value.z);
		_bw.Write(value.w);
	}

	public void Write_Quaternion(Quaternion value) {
		_bw.Write(value.x);
		_bw.Write(value.y);
		_bw.Write(value.z);
		_bw.Write(value.w);
	}

	public void Write_Color32(Color32 value) {
		_bw.Write(value.r);
		_bw.Write(value.g);
		_bw.Write(value.b);
		_bw.Write(value.a);
	}
}
